55. 部署到云端

对于大多数流行云PaaS(平台即服务)提供商,Spring Boot的可执行jars就是为它们准备的。这些提供商往往要求你自己提供容器,它们只负责管理应用的进程(不特别针对Java应用程序),所以它们需要一些中间层来将你的应用适配到云概念中的一个运行进程。

两个流行的云提供商,Heroku和Cloud Foundry,采取一个打包('buildpack')方法。为了启动你的应用程序,不管需要什么,buildpack都会将它们打包到你的部署代码:它可能是一个JDK和一个java调用,也可能是一个内嵌的webserver,或者是一个成熟的应用服务器。buildpack是可插拔的,但你最好尽可能少的对它进行自定义设置。这可以减少不受你控制的功能范围,最小化部署和生产环境的发散。

理想情况下,你的应用就像一个Spring Boot可执行jar,所有运行需要的东西都打包到它内部。

本章节我们将看到在“Getting Started”章节开发的简单应用是怎么在云端运行的。

55.1 Cloud Foundry

如果不指定其他打包方式,Cloud Foundry会启用它提供的默认打包方式。Cloud Foundry的Java buildpack对Spring应用有出色的支持,包括Spring Boot。你可以部署独立的可执行jar应用,也可以部署传统的.war形式的应用。

一旦你构建应用(比如,使用mvn clean package)并安装cf命令行工具,你可以使用下面的cf push命令(将路径指向你编译后的.jar)来部署应用。在发布应用前,确保你已登陆cf命令行客户端

$ cf push acloudyspringtime -p target/demo-0.0.1-SNAPSHOT.jar

查看cf push文档获取更多可选项。如果相同目录下存在manifest.yml,Cloud Foundry会使用它。

就此,cf将开始上传你的应用:

Uploading acloudyspringtime... OK
Preparing to start acloudyspringtime... OK
-----> Downloaded app package (8.9M)
-----> Java Buildpack source: system
-----> Downloading Open JDK 1.7.0_51 from .../x86_64/openjdk-1.7.0_51.tar.gz (1.8s)
       Expanding Open JDK to .java-buildpack/open_jdk (1.2s)
-----> Downloading Spring Auto Reconfiguration from  0.8.7 .../auto-reconfiguration-0.8.7.jar (0.1s)
-----> Uploading droplet (44M)
Checking status of app 'acloudyspringtime'...
  0 of 1 instances running (1 starting)
  ...
  0 of 1 instances running (1 down)
  ...
  0 of 1 instances running (1 starting)
  ...
  1 of 1 instances running (1 running)

App started

恭喜!应用现在处于运行状态!

检验部署应用的状态是很简单的:

$ cf apps
Getting applications in ...
OK

name                 requested state   instances   memory   disk   urls
...
acloudyspringtime    started           1/1         512M     1G     acloudyspringtime.cfapps.io
...

一旦Cloud Foundry意识到你的应用已经部署,你就可以点击给定的应用URI,此处是acloudyspringtime.cfapps.io/

55.1.1 绑定服务

默认情况下,运行应用的元数据和服务连接信息被暴露为应用的环境变量(比如$VCAP_SERVICES),采用这种架构的原因是因为Cloud Foundry多语言特性(任何语言和平台都支持作为buildpack),进程级别的环境变量是语言无关(language agnostic)的。

环境变量并不总是有利于设计最简单的API,所以Spring Boot自动提取它们,然后将这些数据导入能够通过Spring Environment抽象访问的属性里:

@Component
class MyBean implements EnvironmentAware {

    private String instanceId;

    @Override
    public void setEnvironment(Environment environment) {
        this.instanceId = environment.getProperty("vcap.application.instance_id");
    }

    // ...

}

所有的Cloud Foundry属性都以vcap作为前缀,你可以使用vcap属性获取应用信息(比如应用的公共URL)和服务信息(比如数据库证书),具体参考CloudFoundryVcapEnvironmentPostProcessor Javadoc。

Spring Cloud Connectors项目很适合比如配置数据源的任务,Spring Boot为它提供了自动配置支持和一个spring-boot-starter-cloud-connectors starter。

55.2 Heroku

Heroku是另外一个流行的Paas平台,你可以提供一个Procfile来定义Heroku的构建过程,它提供部署应用所需的指令。Heroku为Java应用分配一个端口,确保能够路由到外部URI。

你必须配置你的应用监听正确的端口,下面是用于我们的starter REST应用的Procfile

web: java -Dserver.port=$PORT -jar target/demo-0.0.1-SNAPSHOT.jar

Spring Boot将-D参数作为属性,通过Spring Environment实例访问。server.port配置属性适合于内嵌的Tomcat,Jetty或Undertow实例启用时使用,$PORT环境变量被分配给Heroku Paas使用。

Heroku默认使用Java 1.8,只要你的Maven或Gradle构建时使用相同的版本就没问题(Maven用户可以设置java.version属性)。如果你想使用JDK 1.7,在你的pom.xmlProcfile临近处创建一个system.properties文件,在该文件中添加以下设置:

java.runtime.version=1.7

这就是你需要做的所有内容,对于Heroku部署来说,经常做的工作就是使用git push将代码推送到生产环境。

$ git push heroku master

Initializing repository, done.
Counting objects: 95, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (78/78), done.
Writing objects: 100% (95/95), 8.66 MiB | 606.00 KiB/s, done.
Total 95 (delta 31), reused 0 (delta 0)

-----> Java app detected
-----> Installing OpenJDK 1.8... done
-----> Installing Maven 3.3.1... done
-----> Installing settings.xml... done
-----> executing /app/tmp/cache/.maven/bin/mvn -B
       -Duser.home=/tmp/build_0c35a5d2-a067-4abc-a232-14b1fb7a8229
       -Dmaven.repo.local=/app/tmp/cache/.m2/repository
       -s /app/tmp/cache/.m2/settings.xml -DskipTests=true clean install

       [INFO] Scanning for projects...
       Downloading: http://repo.spring.io/...
       Downloaded: http://repo.spring.io/... (818 B at 1.8 KB/sec)
        ....
       Downloaded: http://s3pository.heroku.com/jvm/... (152 KB at 595.3 KB/sec)
       [INFO] Installing /tmp/build_0c35a5d2-a067-4abc-a232-14b1fb7a8229/target/...
       [INFO] Installing /tmp/build_0c35a5d2-a067-4abc-a232-14b1fb7a8229/pom.xml ...
       [INFO] ------------------------------------------------------------------------
       [INFO] BUILD SUCCESS
       [INFO] ------------------------------------------------------------------------
       [INFO] Total time: 59.358s
       [INFO] Finished at: Fri Mar 07 07:28:25 UTC 2014
       [INFO] Final Memory: 20M/493M
       [INFO] ------------------------------------------------------------------------

-----> Discovering process types
       Procfile declares types -> web

-----> Compressing... done, 70.4MB
-----> Launching... done, v6
       http://agile-sierra-1405.herokuapp.com/ deployed to Heroku

To [email protected]:agile-sierra-1405.git
 * [new branch]      master -> master

现在你的应用已经启动并运行在Heroku。

55.3 Openshift

Openshift是RedHat公共(和企业)PaaS解决方案。和Heroku相似,它也是通过运行被git提交触发的脚本来工作的,所以你可以使用任何你喜欢的方式编写Spring Boot应用启动脚本,只要Java运行时环境可用(这是在Openshift上可以要求的一个标准特性)。为了实现这样的效果,你可以使用DIY Cartridge,并在.openshift/action_scripts下hooks你的仓库:

基本模式如下:

1.确保Java和构建工具已被远程安装,比如使用一个pre_build hook(默认会安装Java和Maven,不会安装Gradle)。

2.使用一个build hook去构建你的jar(使用Maven或Gradle),比如:

#!/bin/bash
cd $OPENSHIFT_REPO_DIR
mvn package -s .openshift/settings.xml -DskipTests=true

3.添加一个调用java -jar …start hook

#!/bin/bash
cd $OPENSHIFT_REPO_DIR
nohup java -jar target/*.jar --server.port=${OPENSHIFT_DIY_PORT} --server.address=${OPENSHIFT_DIY_IP} &

4.使用一个stop hook

#!/bin/bash
source $OPENSHIFT_CARTRIDGE_SDK_BASH
PID=$(ps -ef | grep java.*\.jar | grep -v grep | awk '{ print $2 }')
if [ -z "$PID" ]
then
    client_result "Application is already stopped"
else
    kill $PID
fi

5.将内嵌的服务绑定到平台提供的application.properties定义的环境变量,比如:

spring.datasource.url: jdbc:mysql://${OPENSHIFT_MYSQL_DB_HOST}:${OPENSHIFT_MYSQL_DB_PORT}/${OPENSHIFT_APP_NAME}
spring.datasource.username: ${OPENSHIFT_MYSQL_DB_USERNAME}
spring.datasource.password: ${OPENSHIFT_MYSQL_DB_PASSWORD}

在Openshift的网站上有一篇running Gradle in Openshift博客,如果想使用gradle构建运行的应用可以参考它。

55.4 Boxfuse和Amazon Web Services

Boxfuse的工作机制是将你的Spring Boot可执行jar或war转换进一个最小化的VM镜像,该镜像不需改变就能部署到VirtualBox或AWS。Boxfuse深度集成Spring Boot并使用你的Spring Boot配置文件自动配置端口和健康检查URLs,它将该信息用于产生的镜像及它提供的所有资源(实例,安全分组,可伸缩的负载均衡等)。

一旦创建一个Boxfuse account,并将它连接到你的AWS账号,安装最新版Boxfuse客户端,你就能按照以下操作将Spring Boot应用部署到AWS(首先要确保应用被Maven或Gradle构建过,比如mvn clean package):

$ boxfuse run myapp-1.0.jar -env=prod

更多选项可查看boxfuse run文档,如果当前目录存在一个boxfuse.conf文件,Boxfuse将使用它。

如果你的可执行jar或war包含application-boxfuse.properties文件,Boxfuse默认在启动时会激活一个名为boxfuse的Spring profile,然后在该profile包含的属性基础上构建自己的配置。

此刻boxfuse将为你的应用创建一个镜像并上传到AWS,然后配置并启动需要的资源:

Fusing Image for myapp-1.0.jar ...
Image fused in 00:06.838s (53937 K) -> axelfontaine/myapp:1.0
Creating axelfontaine/myapp ...
Pushing axelfontaine/myapp:1.0 ...
Verifying axelfontaine/myapp:1.0 ...
Creating Elastic IP ...
Mapping myapp-axelfontaine.boxfuse.io to 52.28.233.167 ...
Waiting for AWS to create an AMI for axelfontaine/myapp:1.0 in eu-central-1 (this may take up to 50 seconds) ...
AMI created in 00:23.557s -> ami-d23f38cf
Creating security group boxfuse-sg_axelfontaine/myapp:1.0 ...
Launching t2.micro instance of axelfontaine/myapp:1.0 (ami-d23f38cf) in eu-central-1 ...
Instance launched in 00:30.306s -> i-92ef9f53
Waiting for AWS to boot Instance i-92ef9f53 and Payload to start at http://52.28.235.61/ ...
Payload started in 00:29.266s -> http://52.28.235.61/
Remapping Elastic IP 52.28.233.167 to i-92ef9f53 ...
Waiting 15s for AWS to complete Elastic IP Zero Downtime transition ...
Deployment completed successfully. axelfontaine/myapp:1.0 is up and running at http://myapp-axelfontaine.boxfuse.io/

你的应用现在应该已经在AWS上启动并运行了。

这里有篇在EC2部署Spring Boot应用的博客,Boxfuse官网也有Boxfuse集成Spring Boot文档,你可以拿来作为参考。

55.5 Google App Engine

Google App Engine关联了Servlet 2.5 API,如果不做一些修改你是不能在其上部署Spring应用的,具体查看本指南的Servlet 2.5章节